package com.nav.backend.controller;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nav.backend.pojo.CollectionPath;
import com.nav.backend.pojo.DTO.CollectionPathDTO;
import com.nav.backend.pojo.DTO.TwoPointsSearchDTO;
import com.nav.backend.pojo.RingSearch;
import com.nav.backend.pojo.TwoPointsSearch;
import com.nav.backend.service.NavigateService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * NavigateController 导航控制器类，处理与导航相关的请求
 *
 * @author Seamher
 */
@Validated
@RestController
@RequestMapping("/navigate")
public class NavigateController {

    private final WebClient webClient;

    @Autowired
    private NavigateService navigateService;

    private static final Logger logger = LoggerFactory.getLogger(NavigateController.class);

    public NavigateController(WebClient webClient) {
        this.webClient = webClient;
    }

    /**
     * 解析响应字符串
     *
     * @param body 响应体
     * @return 解析后的结果
     */
    private static String parseResponse(String body, String custom) {
        var jsonObject = JsonParser.parseString(body).getAsJsonObject();
        var paths = jsonObject.getAsJsonArray("paths");
        var i = 0;
        var result = new JsonArray();
        for (var p : paths) {
            i++;
            var path = p.getAsJsonObject();
            var length = path.getAsJsonPrimitive("distance").getAsDouble();
            var time_ms = path.getAsJsonPrimitive("time").getAsLong();

            var coordinates = path.getAsJsonObject("points").getAsJsonArray("coordinates");
            var pointCount = coordinates.size();
            var points = new JsonArray();
            for (var point : coordinates) {
                var coordinate = point.getAsJsonArray();
                var pc = new JsonArray();
                pc.add(coordinate.get(1).getAsDouble());
                pc.add(coordinate.get(0).getAsDouble());
                points.add(pc);
            }

            var greenCount = 0;
            var surface = path.getAsJsonObject("details").getAsJsonArray("surface");
            for (var s : surface) {
                var sur = s.getAsJsonArray();
                var t = sur.get(2).getAsString();
                if (t.equals("grass")) {
                    var start = sur.get(0).getAsInt();
                    var end = sur.get(1).getAsInt();
                    greenCount += end - start;
                }
            }

            var tree_ratio = (double) greenCount / (double) pointCount;
            var default_order = i;
            var distance_order = i;
            var tree_order = i;

            var resultPath = new JsonObject();
            resultPath.add("points", points);
            resultPath.addProperty("length", length);
            resultPath.addProperty("time_ms", time_ms);
            resultPath.addProperty("tree_ratio", tree_ratio);
            resultPath.addProperty("default_order", default_order);
            resultPath.addProperty("distance_order", distance_order);
            resultPath.addProperty("tree_order", tree_order);
            if (!Objects.equals(custom, "")) {
                resultPath.addProperty("vehicle", custom);
            }

            result.add(resultPath);
        }

        return result.toString();
    }

    /**
     * 点到点导航搜索
     *
     * @param twoPointsSearchDTO 两点搜索请求体
     * @return 搜索结果
     */
    @PostMapping("/pointToPoint")
    public String searchPointToPoint(@RequestBody TwoPointsSearchDTO twoPointsSearchDTO) {
        String searchOrigin = twoPointsSearchDTO.getSearch_origin();
        Double searchOriginX = twoPointsSearchDTO.getSearch_origin_x();
        Double searchOriginY = twoPointsSearchDTO.getSearch_origin_y();
        String searchDestination = twoPointsSearchDTO.getSearch_destination();
        Double searchDestinationX = twoPointsSearchDTO.getSearch_destination_x();
        Double searchDestinationY = twoPointsSearchDTO.getSearch_destination_y();
        String account = twoPointsSearchDTO.getAccount();
        List<String> searchWaypoints = twoPointsSearchDTO.getSearch_waypoints();
        List<Double> search_waypoints_x = twoPointsSearchDTO.getSearch_waypoints_x();
        List<Double> search_waypoints_y = twoPointsSearchDTO.getSearch_waypoints_y();
        Double[][] points = new Double[searchWaypoints.size() + 2][2];
        points[0][0] = searchOriginX;
        points[0][1] = searchOriginY;
        if (!searchWaypoints.isEmpty()) {
            for (int i = 0; i < searchWaypoints.size(); i++) {
                points[i + 1][0] = search_waypoints_x.get(i);
                points[i + 1][1] = search_waypoints_y.get(i);
            }
        }
        points[searchWaypoints.size() + 1][0] = searchDestinationX;
        points[searchWaypoints.size() + 1][1] = searchDestinationY;

        logger.info("Point to point search request received for account: {}, origin: {}, destination: {}",
                account, searchOrigin, searchDestination);

        if (null == navigateService.searchBySearchOrigin(searchOrigin, searchDestination)) {
            navigateService.addHistory(searchOrigin, searchOriginX, searchOriginY, searchDestination, searchDestinationX, searchDestinationY, account);
        }

        String url = "http://localhost:8989/route";
        List<String> results = new ArrayList<>();
        String custom;
        for (int i = 0; i < 3; i++) {
            if (i == 0) {
                custom = "custom_foot";
            } else if (i == 1) {
                custom = "custom_bike";
            } else {
                custom = "custom_car";
            }

            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("points", points);
            requestBody.put("profile", custom);
            requestBody.put("details", new String[]{"surface"});
            requestBody.put("locale", "zh_CN");
            requestBody.put("points_encoded", false);
            if (points.length == 2) {
                requestBody.put("algorithm", "alternative_route");
                requestBody.put("alternative_route.max_paths", 5);
                requestBody.put("alternative_route.max_weight_factor", 1.4);
                requestBody.put("alternative_route.max_share_factor", 0.8);
            }

            logger.info("Sending request to external routing service for point to point search.");

            String finalCustom = custom;
            String result = webClient.post()
                    .uri(url)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(Mono.just(requestBody), Map.class)
                    .retrieve().bodyToMono(String.class).map(body -> parseResponse(body, finalCustom)).block();

            results.add(result);
        }

        return "[" + String.join(",", results) + "]";
    }

    /**
     * 环形导航搜索
     *
     * @param ringSearch 环形搜索请求体
     * @return 搜索结果
     */
    @PostMapping("/circular")
    public String searchCircular(@RequestBody RingSearch ringSearch) {
        String searchLocation = ringSearch.getRing_search_location();
        Double searchLocationX = ringSearch.getRing_search_location_x();
        Double searchLocationY = ringSearch.getRing_search_location_y();
        Integer targetDistance = ringSearch.getTarget_distance();
        String account = ringSearch.getAccount();

        logger.info("Circular search request received for account: {}, location: {}, target distance: {}",
                account, searchLocation, targetDistance);

        if (null == navigateService.searchBySearchLocation(searchLocation)) {
            navigateService.addRingHistory(searchLocation, searchLocationX, searchLocationY, targetDistance, account);
        }

        String url = "http://localhost:8989/route";

        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("round_trip.distance", targetDistance);
        requestBody.put("points", new double[][]{{searchLocationX, searchLocationY}});
        requestBody.put("profile", "custom_foot");
        requestBody.put("details", new String[]{"surface"});
        requestBody.put("locale", "zh_CN");
        requestBody.put("algorithm", "round_trip");
        requestBody.put("points_encoded", false);
        requestBody.put("ch.disable", true);

        logger.info("Sending request to external routing service for circular search.");

        return webClient.post()
                .uri(url)
                .contentType(MediaType.APPLICATION_JSON)
                .body(Mono.just(requestBody), Map.class)
                .retrieve().bodyToMono(String.class).map(body -> parseResponse(body, "")).block();
    }

    /**
     * 起终点评分搜索
     *
     * @param twoPointsSearch 起终点评分搜索请求体
     * @return 搜索结果
     */
    @PostMapping("/mark")
    public ResponseEntity<CollectionPathDTO> mark(@RequestBody TwoPointsSearch twoPointsSearch) {
        Double searchOriginX = twoPointsSearch.getSearch_origin_x();
        Double searchOriginY = twoPointsSearch.getSearch_origin_y();
        Double searchDestinationX = twoPointsSearch.getSearch_destination_x();
        Double searchDestinationY = twoPointsSearch.getSearch_destination_y();

        List<CollectionPath> collectionPathList = navigateService.mark(searchOriginX, searchOriginY, searchDestinationX, searchDestinationY, 0.0000648);
        CollectionPathDTO collectionPathDTO = new CollectionPathDTO(collectionPathList);
        return ResponseEntity.ok(collectionPathDTO);
    }
}
